defmodule ProgContest.BlockingQueue do
  use GenServer

  @empty :queue.new()

  @spec start_link(any) :: :ignore | {:error, any} | {:ok, pid}
  def start_link(_) do
    GenServer.start_link(__MODULE__, [])
  end

  @spec push(any) :: :ok
  def push(item) do
    GenServer.cast(server(), {:push, item})
  end

  @spec pop :: any
  def pop do
    GenServer.call(server(), :pop, :infinity)
  end

  @impl true
  def init(_) do
    {:ok, {@empty, @empty}}
  end

  @impl true
  def handle_cast({:push, item}, {items, consumers}) do
    {:noreply, {:queue.in(item, items), consumers}, {:continue, :pop}}
  end

  def handle_cast({:swarm, :end_handoff, {inbound_items, inbound_consumers}}, {items, consumers}) do
    {:noreply, {:queue.join(items, inbound_items), :queue.join(consumers, inbound_consumers)}}
  end

  @impl true
  def handle_call(:pop, from, {items, consumers}) do
    {:noreply, {items, :queue.in(from, consumers)}, {:continue, :pop}}
  end

  def handle_call({:swarm, :begin_handoff}, _from, state) do
    {:reply, {:resume, state}, state}
  end

  @impl true
  def handle_continue(:pop, {@empty, _consumers} = state) do
    {:noreply, state}
  end

  def handle_continue(:pop, {_items, @empty} = state) do
    {:noreply, state}
  end

  def handle_continue(:pop, {items, consumers}) do
    {{:value, item}, items} = :queue.out(items)
    {{:value, consumer}, consumers} = :queue.out(consumers)
    GenServer.reply(consumer, item)
    {:noreply, {items, consumers}, {:continue, :pop}}
  end

  defp server do
    Swarm.whereis_name(__MODULE__)
  end
end
